import transformers from '@kubb/core/transformers'

import type { Schema, SchemaKeywordBase, SchemaMapper } from '@kubb/plugin-oas'
import { isKeyword, SchemaGenerator, type SchemaKeywordMapper, type SchemaTree, schemaKeywords } from '@kubb/plugin-oas'

//TODO add zodKeywordMapper as function that returns 3 versions: v3, v4 and v4 mini, this can also be used to have the custom mapping(see object type)
// also include shouldCoerce

/**
 * Helper to build string/array length constraint checks for Zod Mini mode
 */
function buildLengthChecks(min?: number, max?: number): string[] {
  const checks: string[] = []
  if (min !== undefined) checks.push(`z.minLength(${min})`)
  if (max !== undefined) checks.push(`z.maxLength(${max})`)
  return checks
}

const zodKeywordMapper = {
  any: () => 'z.any()',
  unknown: () => 'z.unknown()',
  void: () => 'z.void()',
  number: (coercion?: boolean, min?: number, max?: number, exclusiveMinimum?: number, exclusiveMaximum?: number, mini?: boolean) => {
    if (mini) {
      const checks: string[] = []
      if (min !== undefined) checks.push(`z.minimum(${min})`)
      if (max !== undefined) checks.push(`z.maximum(${max})`)
      if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
      if (exclusiveMaximum !== undefined) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`)
      if (checks.length > 0) {
        return `z.number().check(${checks.join(', ')})`
      }
      return 'z.number()'
    }
    return [
      coercion ? 'z.coerce.number()' : 'z.number()',
      min !== undefined ? `.min(${min})` : undefined,
      max !== undefined ? `.max(${max})` : undefined,
      exclusiveMinimum !== undefined ? `.gt(${exclusiveMinimum})` : undefined,
      exclusiveMaximum !== undefined ? `.lt(${exclusiveMaximum})` : undefined,
    ]
      .filter(Boolean)
      .join('')
  },
  integer: (coercion?: boolean, min?: number, max?: number, version: '3' | '4' = '3', exclusiveMinimum?: number, exclusiveMaximum?: number, mini?: boolean) => {
    if (mini) {
      const checks: string[] = []
      if (min !== undefined) checks.push(`z.minimum(${min})`)
      if (max !== undefined) checks.push(`z.maximum(${max})`)
      if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
      if (exclusiveMaximum !== undefined) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`)
      if (checks.length > 0) {
        return `z.int().check(${checks.join(', ')})`
      }
      return 'z.int()'
    }
    return [
      coercion ? 'z.coerce.number().int()' : version === '4' ? 'z.int()' : 'z.number().int()',
      min !== undefined ? `.min(${min})` : undefined,
      max !== undefined ? `.max(${max})` : undefined,
      exclusiveMinimum !== undefined ? `.gt(${exclusiveMinimum})` : undefined,
      exclusiveMaximum !== undefined ? `.lt(${exclusiveMaximum})` : undefined,
    ]
      .filter(Boolean)
      .join('')
  },
  interface: (value?: string, strict?: boolean) => {
    if (strict) {
      return `z.strictInterface({
    ${value}
    })`
    }
    return `z.interface({
    ${value}
    })`
  },
  object: (value?: string, strict?: boolean, version: '3' | '4' = '3') => {
    if (version === '4' && strict) {
      return `z.strictObject({
    ${value}
    })`
    }

    if (strict) {
      return `z.object({
    ${value}
    }).strict()`
    }

    return `z.object({
    ${value}
    })`
  },
  string: (coercion?: boolean, min?: number, max?: number, mini?: boolean) => {
    if (mini) {
      const checks = buildLengthChecks(min, max)
      if (checks.length > 0) {
        return `z.string().check(${checks.join(', ')})`
      }
      return 'z.string()'
    }
    return [coercion ? 'z.coerce.string()' : 'z.string()', min !== undefined ? `.min(${min})` : undefined, max !== undefined ? `.max(${max})` : undefined]
      .filter(Boolean)
      .join('')
  },
  //support for discriminatedUnion
  boolean: () => 'z.boolean()',
  undefined: () => 'z.undefined()',
  nullable: (value?: string) => {
    if (value) {
      return `z.nullable(${value})`
    }
    return '.nullable()'
  },
  null: () => 'z.null()',
  nullish: (value?: string) => {
    if (value) {
      return `z.nullish(${value})`
    }
    return '.nullish()'
  },
  array: (items: string[] = [], min?: number, max?: number, unique?: boolean, mini?: boolean) => {
    if (mini) {
      const checks = buildLengthChecks(min, max)
      if (unique) checks.push(`z.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`)
      if (checks.length > 0) {
        return `z.array(${items?.join('')}).check(${checks.join(', ')})`
      }
      return `z.array(${items?.join('')})`
    }
    return [
      `z.array(${items?.join('')})`,
      min !== undefined ? `.min(${min})` : undefined,
      max !== undefined ? `.max(${max})` : undefined,
      unique ? `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : undefined,
    ]
      .filter(Boolean)
      .join('')
  },
  tuple: (items: string[] = []) => `z.tuple([${items?.join(', ')}])`,
  enum: (items: string[] = []) => `z.enum([${items?.join(', ')}])`,
  union: (items: string[] = []) => `z.union([${items?.join(', ')}])`,
  const: (value?: string | number | boolean) => `z.literal(${value ?? ''})`,
  /**
   * ISO 8601
   */
  datetime: (offset = false, local = false, version: '3' | '4' = '3', mini?: boolean) => {
    // Zod Mini doesn't support .datetime() method, use plain string
    if (mini) {
      return 'z.string()'
    }

    if (offset) {
      return version === '4' ? `z.iso.datetime({ offset: ${offset} })` : `z.string().datetime({ offset: ${offset} })`
    }

    if (local) {
      return version === '4' ? `z.iso.datetime({ local: ${local} })` : `z.string().datetime({ local: ${local} })`
    }

    return 'z.string().datetime()'
  },
  /**
   * Type `'date'` Date
   * Type `'string'` ISO date format (YYYY-MM-DD)
   * @default ISO date format (YYYY-MM-DD)
   */
  date: (type: 'date' | 'string' = 'string', coercion?: boolean, version: '3' | '4' = '3') => {
    if (type === 'string') {
      return version === '4' ? 'z.iso.date()' : 'z.string().date()'
    }

    if (coercion) {
      return 'z.coerce.date()'
    }

    return 'z.date()'
  },
  /**
   * Type `'date'` Date
   * Type `'string'` ISO time format (HH:mm:ss[.SSSSSS])
   * @default ISO time format (HH:mm:ss[.SSSSSS])
   */
  time: (type: 'date' | 'string' = 'string', coercion?: boolean, version: '3' | '4' = '3') => {
    if (type === 'string') {
      return version === '4' ? 'z.iso.time()' : 'z.string().time()'
    }

    if (coercion) {
      return 'z.coerce.date()'
    }

    return 'z.date()'
  },
  uuid: (coercion?: boolean, version: '3' | '4' = '3', min?: number, max?: number, mini?: boolean) => {
    if (mini) {
      const checks = buildLengthChecks(min, max)
      if (checks.length > 0) {
        return `z.uuid().check(${checks.join(', ')})`
      }
      return 'z.uuid()'
    }
    return [
      coercion ? (version === '4' ? 'z.uuid()' : 'z.coerce.string().uuid()') : version === '4' ? 'z.uuid()' : 'z.string().uuid()',
      min !== undefined ? `.min(${min})` : undefined,
      max !== undefined ? `.max(${max})` : undefined,
    ]
      .filter(Boolean)
      .join('')
  },
  url: (coercion?: boolean, version: '3' | '4' = '3', min?: number, max?: number, mini?: boolean) => {
    if (mini) {
      const checks = buildLengthChecks(min, max)
      if (checks.length > 0) {
        return `z.url().check(${checks.join(', ')})`
      }
      return 'z.url()'
    }
    return [
      coercion ? (version === '4' ? 'z.url()' : 'z.coerce.string().url()') : version === '4' ? 'z.url()' : 'z.string().url()',
      min !== undefined ? `.min(${min})` : undefined,
      max !== undefined ? `.max(${max})` : undefined,
    ]
      .filter(Boolean)
      .join('')
  },
  default: (value?: string | number | true | object, innerSchema?: string, mini?: boolean) => {
    if (mini && innerSchema) {
      const defaultValue = typeof value === 'object' ? '{}' : (value ?? '')
      return `z._default(${innerSchema}, ${defaultValue})`
    }
    if (typeof value === 'object') {
      return '.default({})'
    }
    return `.default(${value ?? ''})`
  },
  and: (items: string[] = []) => items?.map((item) => `.and(${item})`).join(''),
  describe: (value = '', innerSchema?: string, mini?: boolean) => {
    if (mini) {
      return undefined
    }

    if (innerSchema) {
      return `z.describe(${innerSchema}, ${value})`
    }
    return `.describe(${value})`
  },
  max: undefined,
  min: undefined,
  optional: (value?: string) => {
    if (value) {
      return `z.optional(${value})`
    }
    return '.optional()'
  },
  matches: (value = '', coercion?: boolean, mini?: boolean) => {
    if (mini) {
      return `z.string().check(z.regex(${value}))`
    }
    return coercion ? `z.coerce.string().regex(${value})` : `z.string().regex(${value})`
  },
  email: (coercion?: boolean, version: '3' | '4' = '3', min?: number, max?: number, mini?: boolean) => {
    if (mini) {
      const checks = buildLengthChecks(min, max)
      if (checks.length > 0) {
        return `z.email().check(${checks.join(', ')})`
      }
      return 'z.email()'
    }
    return [
      coercion ? (version === '4' ? 'z.email()' : 'z.coerce.string().email()') : version === '4' ? 'z.email()' : 'z.string().email()',
      min !== undefined ? `.min(${min})` : undefined,
      max !== undefined ? `.max(${max})` : undefined,
    ]
      .filter(Boolean)
      .join('')
  },
  firstName: undefined,
  lastName: undefined,
  password: undefined,
  phone: undefined,
  readOnly: undefined,
  writeOnly: undefined,
  ref: (value?: string) => {
    if (!value) {
      return undefined
    }

    return `z.lazy(() => ${value})`
  },
  blob: () => 'z.instanceof(File)',
  deprecated: undefined,
  example: undefined,
  schema: undefined,
  catchall: (value?: string, mini?: boolean) => {
    // Zod Mini doesn't support .catchall() method
    if (mini) {
      return undefined
    }
    return value ? `.catchall(${value})` : undefined
  },
  name: undefined,
  exclusiveMinimum: undefined,
  exclusiveMaximum: undefined,
} satisfies SchemaMapper<string | null | undefined>

/**
 * @link based on https://github.com/cellular/oazapfts/blob/7ba226ebb15374e8483cc53e7532f1663179a22c/src/codegen/generate.ts#L398
 */

export function sort(items?: Schema[]): Schema[] {
  const order: string[] = [
    schemaKeywords.string,
    schemaKeywords.datetime,
    schemaKeywords.date,
    schemaKeywords.time,
    schemaKeywords.tuple,
    schemaKeywords.number,
    schemaKeywords.object,
    schemaKeywords.enum,
    schemaKeywords.url,
    schemaKeywords.email,
    schemaKeywords.firstName,
    schemaKeywords.lastName,
    schemaKeywords.password,
    schemaKeywords.matches,
    schemaKeywords.uuid,
    schemaKeywords.null,
    schemaKeywords.min,
    schemaKeywords.max,
    schemaKeywords.default,
    schemaKeywords.describe,
    schemaKeywords.optional,
    schemaKeywords.nullable,
    schemaKeywords.nullish,
  ]

  if (!items) {
    return []
  }

  return transformers.orderBy(items, [(v) => order.indexOf(v.keyword)], ['asc'])
}

type MiniModifiers = {
  hasOptional?: boolean
  hasNullable?: boolean
  hasNullish?: boolean
  defaultValue?: string | number | true | object
}

/**
 * Keywords that represent modifiers for mini mode
 * These are separated from the base schema and wrapped around it
 * Note: describe is included to filter it out, but won't be wrapped (Zod Mini doesn't support describe)
 */
export const miniModifierKeywords = [schemaKeywords.optional, schemaKeywords.nullable, schemaKeywords.nullish, schemaKeywords.default, schemaKeywords.describe]

/**
 * Extracts mini mode modifiers from a schemas array
 * This can be reused by other parsers (e.g., valibot) that need similar functionality
 * Note: describe is not included as Zod Mini doesn't support it
 */
export function extractMiniModifiers(schemas: Schema[]): MiniModifiers {
  const defaultSchema = schemas.find((item) => isKeyword(item, schemaKeywords.default)) as { keyword: string; args: unknown } | undefined

  return {
    hasOptional: schemas.some((item) => isKeyword(item, schemaKeywords.optional)),
    hasNullable: schemas.some((item) => isKeyword(item, schemaKeywords.nullable)),
    hasNullish: schemas.some((item) => isKeyword(item, schemaKeywords.nullish)),
    defaultValue: defaultSchema?.args as string | number | true | object | undefined,
  }
}

/**
 * Filters out modifier keywords from schemas for mini mode base schema parsing
 * This can be reused by other parsers (e.g., valibot) that need similar functionality
 */
export function filterMiniModifiers(schemas: Schema[]): Schema[] {
  return schemas.filter((item) => !miniModifierKeywords.some((keyword) => isKeyword(item, keyword)))
}

/**
 * Wraps an output string with Zod Mini functional modifiers
 * Order: default (innermost) -> nullable -> optional (outermost)
 * OR: default -> nullish
 * Note: describe is not supported in Zod Mini and is skipped
 */
export function wrapWithMiniModifiers(output: string, modifiers: MiniModifiers): string {
  let result = output

  // Apply default first (innermost wrapper)
  if (modifiers.defaultValue !== undefined) {
    result = zodKeywordMapper.default(modifiers.defaultValue, result, true)!
  }

  // Apply nullish, nullable, or optional (outer wrappers for optionality)
  if (modifiers.hasNullish) {
    result = zodKeywordMapper.nullish(result)!
  } else {
    if (modifiers.hasNullable) {
      result = zodKeywordMapper.nullable(result)!
    }
    if (modifiers.hasOptional) {
      result = zodKeywordMapper.optional(result)!
    }
  }

  return result
}

const shouldCoerce = (coercion: ParserOptions['coercion'] | undefined, type: 'dates' | 'strings' | 'numbers'): boolean => {
  if (coercion === undefined) {
    return false
  }
  if (typeof coercion === 'boolean') {
    return coercion
  }

  return !!coercion[type]
}

type ParserOptions = {
  mapper?: Record<string, string>
  coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
  wrapOutput?: (opts: { output: string; schema: any }) => string | undefined
  version: '3' | '4'
  skipLazyForRefs?: boolean
  mini?: boolean
}

export function parse({ schema, parent, current, name, siblings }: SchemaTree, options: ParserOptions): string | undefined {
  const value = zodKeywordMapper[current.keyword as keyof typeof zodKeywordMapper]

  // Early exit: if siblings contain both matches and ref → skip matches entirely
  const hasMatches = siblings.some((it) => isKeyword(it, schemaKeywords.matches))
  const hasRef = siblings.some((it) => isKeyword(it, schemaKeywords.ref))

  if (hasMatches && hasRef && isKeyword(current, schemaKeywords.matches)) {
    return undefined // strip matches
  }

  if (!value) {
    return undefined
  }

  if (isKeyword(current, schemaKeywords.union)) {
    // zod union type needs at least 2 items
    if (Array.isArray(current.args) && current.args.length === 1) {
      return parse({ schema, parent, name, current: current.args[0] as Schema, siblings }, options)
    }
    if (Array.isArray(current.args) && !current.args.length) {
      return ''
    }

    return zodKeywordMapper.union(
      sort(current.args)
        .map((it, _index, siblings) => parse({ schema, parent: current, name, current: it, siblings }, options))
        .filter(Boolean),
    )
  }

  if (isKeyword(current, schemaKeywords.and)) {
    const items = sort(current.args)
      .filter((schema: Schema) => {
        return ![schemaKeywords.optional, schemaKeywords.describe].includes(schema.keyword as typeof schemaKeywords.describe)
      })
      .map((it: Schema, _index, siblings) => parse({ schema, parent: current, name, current: it, siblings }, options))
      .filter(Boolean)

    return `${items.slice(0, 1)}${zodKeywordMapper.and(items.slice(1))}`
  }

  if (isKeyword(current, schemaKeywords.array)) {
    return zodKeywordMapper.array(
      sort(current.args.items)
        .map((it, _index, siblings) => {
          return parse({ schema, parent: current, name, current: it, siblings }, options)
        })
        .filter(Boolean),
      current.args.min,
      current.args.max,
      current.args.unique,
      options.mini,
    )
  }

  if (isKeyword(current, schemaKeywords.enum)) {
    if (current.args.asConst) {
      if (current.args.items.length === 1) {
        const child = {
          keyword: schemaKeywords.const,
          args: current.args.items[0],
        }
        return parse({ schema, parent: current, name, current: child, siblings: [child] }, options)
      }

      return zodKeywordMapper.union(
        current.args.items
          .map((schema) => ({
            keyword: schemaKeywords.const,
            args: schema,
          }))
          .map((it, _index, siblings) => {
            return parse({ schema, parent: current, name, current: it, siblings }, options)
          })
          .filter(Boolean),
      )
    }

    return zodKeywordMapper.enum(
      current.args.items.map((schema) => {
        if (schema.format === 'boolean') {
          return transformers.stringify(schema.value)
        }

        if (schema.format === 'number') {
          return transformers.stringify(schema.value)
        }
        return transformers.stringify(schema.value)
      }),
    )
  }

  if (isKeyword(current, schemaKeywords.ref)) {
    // Skip z.lazy wrapper if skipLazyForRefs is true (e.g., inside v4 getters)
    if (options.skipLazyForRefs) {
      return current.args?.name
    }
    return zodKeywordMapper.ref(current.args?.name)
  }

  if (isKeyword(current, schemaKeywords.object)) {
    const propertyEntries = Object.entries(current.args?.properties || {}).filter((item) => {
      const schema = item[1]
      return schema && typeof schema.map === 'function'
    })

    const properties = propertyEntries
      .map(([propertyName, schemas]) => {
        const nameSchema = schemas.find((it) => it.keyword === schemaKeywords.name) as SchemaKeywordMapper['name']
        const isNullable = schemas.some((it) => isKeyword(it, schemaKeywords.nullable))
        const isNullish = schemas.some((it) => isKeyword(it, schemaKeywords.nullish))
        const isOptional = schemas.some((it) => isKeyword(it, schemaKeywords.optional))
        const hasRef = !!SchemaGenerator.find(schemas, schemaKeywords.ref)

        const mappedName = nameSchema?.args || propertyName

        // custom mapper(pluginOptions)
        if (options.mapper?.[mappedName]) {
          return `"${propertyName}": ${options.mapper?.[mappedName]}`
        }

        const baseSchemaOutput = sort(schemas)
          .filter((schema) => {
            return !isKeyword(schema, schemaKeywords.optional) && !isKeyword(schema, schemaKeywords.nullable) && !isKeyword(schema, schemaKeywords.nullish)
          })
          .map((it) => {
            // For v4 with refs, skip z.lazy wrapper since the getter provides lazy evaluation
            const skipLazyForRefs = options.version === '4' && hasRef
            return parse({ schema, parent: current, name, current: it, siblings: schemas }, { ...options, skipLazyForRefs })
          })
          .filter(Boolean)
          .join('')

        const objectValue = options.wrapOutput
          ? options.wrapOutput({ output: baseSchemaOutput, schema: schema?.properties?.[propertyName] }) || baseSchemaOutput
          : baseSchemaOutput

        if (options.version === '4' && hasRef) {
          // In mini mode, use functional wrappers instead of chainable methods
          if (options.mini) {
            // both optional and nullable
            if (isNullish) {
              return `get "${propertyName}"(){
                return ${zodKeywordMapper.nullish(objectValue)}
              }`
            }

            // undefined
            if (isOptional) {
              return `get "${propertyName}"(){
                return ${zodKeywordMapper.optional(objectValue)}
              }`
            }

            // null
            if (isNullable) {
              return `get "${propertyName}"(){
                return ${zodKeywordMapper.nullable(objectValue)}
              }`
            }

            return `get "${propertyName}"(){
                return ${objectValue}
              }`
          }

          // Non-mini mode uses chainable methods
          // both optional and nullable
          if (isNullish) {
            return `get "${propertyName}"(){
                return ${objectValue}${zodKeywordMapper.nullish()}
              }`
          }

          // undefined
          if (isOptional) {
            return `get "${propertyName}"(){
                return ${objectValue}${zodKeywordMapper.optional()}
              }`
          }

          // null
          if (isNullable) {
            return `get "${propertyName}"(){
               return ${objectValue}${zodKeywordMapper.nullable()}
              }`
          }

          return `get "${propertyName}"(){
                return ${objectValue}
              }`
        }

        // both optional and nullable
        if (isNullish) {
          return `"${propertyName}": ${objectValue}${zodKeywordMapper.nullish()}`
        }

        // undefined
        if (isOptional) {
          return `"${propertyName}": ${zodKeywordMapper.optional(objectValue)}`
        }

        // null
        if (isNullable) {
          return `"${propertyName}": ${zodKeywordMapper.nullable(objectValue)}`
        }

        return `"${propertyName}": ${objectValue}`
      })
      .join(',\n')

    const additionalProperties = current.args?.additionalProperties?.length
      ? current.args.additionalProperties
          .map((it, _index, siblings) => parse({ schema, parent: current, name, current: it, siblings }, options))
          .filter(Boolean)
          .join('')
      : undefined

    const text = [
      zodKeywordMapper.object(properties, current.args?.strict, options.version),
      additionalProperties ? zodKeywordMapper.catchall(additionalProperties, options.mini) : undefined,
    ].filter(Boolean)

    return text.join('')
  }

  if (isKeyword(current, schemaKeywords.tuple)) {
    return zodKeywordMapper.tuple(
      current.args.items.map((it, _index, siblings) => parse({ schema, parent: current, name, current: it, siblings }, options)).filter(Boolean),
    )
  }

  if (isKeyword(current, schemaKeywords.const)) {
    if (current.args.format === 'number' && current.args.value !== undefined) {
      return zodKeywordMapper.const(Number(current.args.value))
    }

    if (current.args.format === 'boolean' && current.args.value !== undefined) {
      return zodKeywordMapper.const(current.args.value)
    }
    return zodKeywordMapper.const(transformers.stringify(current.args.value))
  }

  if (isKeyword(current, schemaKeywords.matches)) {
    if (current.args) {
      return zodKeywordMapper.matches(transformers.toRegExpString(current.args, null), shouldCoerce(options.coercion, 'strings'), options.mini)
    }
  }

  if (isKeyword(current, schemaKeywords.default)) {
    // In mini mode, default is handled by wrapWithMiniModifiers
    if (options.mini) {
      return undefined
    }
    if (current.args) {
      return zodKeywordMapper.default(current.args)
    }
  }

  if (isKeyword(current, schemaKeywords.describe)) {
    if (current.args) {
      return zodKeywordMapper.describe(transformers.stringify(current.args.toString()), undefined, options.mini)
    }
  }

  if (isKeyword(current, schemaKeywords.string)) {
    const minSchema = SchemaGenerator.find(siblings, schemaKeywords.min)
    const maxSchema = SchemaGenerator.find(siblings, schemaKeywords.max)

    return zodKeywordMapper.string(shouldCoerce(options.coercion, 'strings'), minSchema?.args, maxSchema?.args, options.mini)
  }

  if (isKeyword(current, schemaKeywords.uuid)) {
    const minSchema = SchemaGenerator.find(siblings, schemaKeywords.min)
    const maxSchema = SchemaGenerator.find(siblings, schemaKeywords.max)

    return zodKeywordMapper.uuid(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
  }

  if (isKeyword(current, schemaKeywords.email)) {
    const minSchema = SchemaGenerator.find(siblings, schemaKeywords.min)
    const maxSchema = SchemaGenerator.find(siblings, schemaKeywords.max)

    return zodKeywordMapper.email(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
  }

  if (isKeyword(current, schemaKeywords.url)) {
    const minSchema = SchemaGenerator.find(siblings, schemaKeywords.min)
    const maxSchema = SchemaGenerator.find(siblings, schemaKeywords.max)

    return zodKeywordMapper.url(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
  }

  if (isKeyword(current, schemaKeywords.number)) {
    const minSchema = SchemaGenerator.find(siblings, schemaKeywords.min)
    const maxSchema = SchemaGenerator.find(siblings, schemaKeywords.max)

    const exclusiveMinimumSchema = SchemaGenerator.find(siblings, schemaKeywords.exclusiveMinimum)
    const exclusiveMaximumSchema = SchemaGenerator.find(siblings, schemaKeywords.exclusiveMaximum)
    return zodKeywordMapper.number(
      shouldCoerce(options.coercion, 'numbers'),
      minSchema?.args,
      maxSchema?.args,
      exclusiveMinimumSchema?.args,
      exclusiveMaximumSchema?.args,
      options.mini,
    )
  }

  if (isKeyword(current, schemaKeywords.integer)) {
    const minSchema = SchemaGenerator.find(siblings, schemaKeywords.min)
    const maxSchema = SchemaGenerator.find(siblings, schemaKeywords.max)

    const exclusiveMinimumSchema = SchemaGenerator.find(siblings, schemaKeywords.exclusiveMinimum)
    const exclusiveMaximumSchema = SchemaGenerator.find(siblings, schemaKeywords.exclusiveMaximum)
    return zodKeywordMapper.integer(
      shouldCoerce(options.coercion, 'numbers'),
      minSchema?.args,
      maxSchema?.args,
      options.version,
      exclusiveMinimumSchema?.args,
      exclusiveMaximumSchema?.args,
      options.mini,
    )
  }

  if (isKeyword(current, schemaKeywords.datetime)) {
    return zodKeywordMapper.datetime(current.args.offset, current.args.local, options.version, options.mini)
  }

  if (isKeyword(current, schemaKeywords.date)) {
    return zodKeywordMapper.date(current.args.type, shouldCoerce(options.coercion, 'dates'), options.version)
  }

  if (isKeyword(current, schemaKeywords.time)) {
    return zodKeywordMapper.time(current.args.type, shouldCoerce(options.coercion, 'dates'), options.version)
  }

  if (current.keyword in zodKeywordMapper && 'args' in current) {
    const value = zodKeywordMapper[current.keyword as keyof typeof zodKeywordMapper] as (typeof zodKeywordMapper)['const']

    return value((current as SchemaKeywordBase<unknown>).args as any)
  }

  if (current.keyword in zodKeywordMapper) {
    return value()
  }

  return undefined
}
